Skip to content

fix(kafka-python): record actual producer partition from send() future#4858

Open
bourbonkk wants to merge 4 commits into
open-telemetry:mainfrom
bourbonkk:fix/kafka-python-partition-4625
Open

fix(kafka-python): record actual producer partition from send() future#4858
bourbonkk wants to merge 4 commits into
open-telemetry:mainfrom
bourbonkk:fix/kafka-python-partition-4625

Conversation

@bourbonkk

@bourbonkk bourbonkk commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes #4625

The kafka-python producer instrumentation recorded messaging.kafka.partition from a value it computed before send(), by calling instance._partition() itself. With the default partitioner and no key, _partition() selects a partition at random; send() then calls _partition() again independently and picks a different partition, so the span attribute frequently did not match where the message was actually delivered.

This reads the partition back from the FutureRecordMetadata returned by send() (_produce_future.topic_partition), which is the partition send() actually assigned. It is accurate for all cases (explicit partition, key-based, and random keyless), and removes the speculative _wait_on_metadata() / _serialize() / _partition() calls the instrumentation previously made. If the attribute can't be read it is omitted rather than recording a wrong value.

The producer span now wraps the send() call so the partition can be read from its result before the span ends.

Note: an earlier revision of this PR took a narrower approach (only record the partition when deterministic). It was replaced with reading the real partition from the future — see commit history.

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • Unit tests in tests/test_utils.py: partition read from the future (incl. 0), a check against a real kafka-python FutureRecordMetadata, graceful None when internals are unavailable, and _enrich_span recording/omitting the attribute.
  • Verified _produce_future.topic_partition exists across the supported range (kafka-python 2.0.3 … 3.0.8).

Does This PR Require a Core Repo Change?

  • No.

Checklist:

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

@bourbonkk
bourbonkk requested a review from a team as a code owner July 23, 2026 06:25
@linux-foundation-easycla

linux-foundation-easycla Bot commented Jul 23, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ login: bourbonkk / name: allen (8823299)

…r spans

When neither a key nor an explicit partition is provided, the
DefaultPartitioner selects a partition at random. The instrumentation
called `_partition()` to record it in the span, but the producer's
`send()` calls `_partition()` again independently and may pick a
different partition, so `messaging.kafka.partition` frequently did not
match where the message was actually delivered.

Record the partition only when it can be determined without changing
where the message lands (explicit partition, or key-based hash which is
deterministic) and omit the attribute for the random keyless case.

Fixes open-telemetry#4625

Assisted-by: Claude Opus 4.8
Signed-off-by: allen <allen.k1m@kakaocorp.com>
@bourbonkk
bourbonkk force-pushed the fix/kafka-python-partition-4625 branch from 10cc6d8 to 8823299 Compare July 23, 2026 06:28
Instead of estimating the destination partition before send() (which,
with the default partitioner and no key, runs a random choice that send()
then redoes and diverges from), read the partition back from the
FutureRecordMetadata returned by send() via _produce_future.topic_partition.
This is accurate for explicit, key-based and random-partition cases alike,
and removes the speculative _wait_on_metadata()/_serialize()/_partition()
calls the instrumentation previously made.

The producer span now wraps the send() call so the partition can be read
from its result before the span ends.

Fixes open-telemetry#4625

Assisted-by: Claude Opus 4.8
Signed-off-by: allen <allen.k1m@kakaocorp.com>
@bourbonkk bourbonkk reopened this Jul 24, 2026
@bourbonkk bourbonkk changed the title fix(kafka-python): only record known destination partition in producer spans fix(kafka-python): record actual producer partition from send() future Jul 24, 2026
@bourbonkk

Copy link
Copy Markdown
Contributor Author

Verified against a live Kafka broker (single-node, topic with 12 partitions, 40 messages sent with no key and no explicit partition, so the DefaultPartitioner assigns randomly):

  • New approach (this PR): the partition recorded on the span matched the actual delivered partition (read from future.get().partition) for all 40/40 messages.
  • Old approach (estimating via _partition() before send()): the recorded partition was wrong for 35/40 messages (~88%), e.g. estimate=9 → actual=3, estimate=0 → actual=8.

Also worth noting: the old code's _wait_on_metadata(topic, max_block_ms / 1000.0) call raises KafkaTimeoutError on kafka-python 2.3.2, since that argument is now milliseconds — so the pre-send estimation path was already broken on newer versions. This PR removes that call entirely.

Address review nits: use `list[str]` and `int | None` for the annotations
touched in this change.

Assisted-by: Claude Opus 4.8
Signed-off-by: allen <allen.k1m@kakaocorp.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Incorrect partition in opentelemetry-instrumentation-kafka-python

2 participants